﻿using HybridLab.Virtual.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Xml.XPath;

namespace HybridLab.Virtual.HyperV
{
	public class HyperVVirtualMachine : IVirtualMachine
	{
		private ManagementObject machineObject;

		private List<ManagementObject> snapshots;

		public string Name
		{
			get
			{
				return machineObject["ElementName"].ToString();
			}
		}

		public string DnsName
		{
			get
			{
				return GetDnsName();
			}
		}

		private HyperVHost host;
		public IVirtualHost Host 
		{
			get
			{
				return host;
			}
		}

		public HyperVVirtualMachine(ManagementObject machineObject, HyperVHost host)
		{
			this.machineObject = machineObject;
			this.host = host;
		}

		internal List<ManagementObject> SnapshotObjects
		{
			get
			{
				if (snapshots == null)
				{
					InitSnapshots();
				}
				return snapshots;
			}
		}

		public List<string> Snapshots
		{
			get
			{
				return SnapshotObjects.Select(s => s["ElementName"].ToString()).ToList();
			}
		}

		
		public void ApplySnapshot(string snapshotName)
		{
			if (State == VMState.On)
			{
				if (!ChangeVMState(VMState.Saved))
				{
					throw new HyperVException("Could not save VM before applying snapshot");
				}
			}
			using (ManagementObject snapshotService = HyperVUtils.GetVirtualMachineSnapshotService(host.RootScope))
			using (ManagementBaseObject inParams = snapshotService.GetMethodParameters(HyperVUtils.ApplySnapshot))
			{
				inParams["Snapshot"] = SnapshotObjects.Single(s => s["ElementName"].ToString().Equals(snapshotName, StringComparison.OrdinalIgnoreCase));

				HyperVUtils.InvokeMethod(snapshotService, HyperVUtils.ApplySnapshot, inParams, true);
			}
			ChangeVMState(VMState.On);
		}

		public void CreateSnapshot(string snapshotPrefix)
		{
			if (string.IsNullOrEmpty(snapshotPrefix))
			{
				throw new ArgumentNullException("snapshotPrefix");
			}
			using (ManagementObject snapshotService = HyperVUtils.GetVirtualMachineSnapshotService(host.RootScope))
			using (ManagementBaseObject inParams = snapshotService.GetMethodParameters(HyperVUtils.CreateSnapshot))
			{
				inParams["AffectedSystem"] = machineObject.Path.Path;
				inParams["SnapshotType"] = HyperVUtils.FullSnapshot;
				
				var result = HyperVUtils.InvokeMethod(snapshotService, HyperVUtils.CreateSnapshot, inParams, true);
				var snapshot = result.Job.RawJob.GetRelated(HyperVUtils.SettingsData).OfType<ManagementObject>().Single();

				using (var otherInParams = host.VMManagementService.GetMethodParameters(HyperVUtils.ModifySystemSettings))
				{
					snapshot["ElementName"] = snapshotPrefix;
					otherInParams["SystemSettings"] = snapshot.GetText(TextFormat.CimDtd20);
					result = HyperVUtils.InvokeMethod(host.VMManagementService, HyperVUtils.ModifySystemSettings, otherInParams, true);
				}
			}
			InitSnapshots();
		}

		private bool ChangeVMState(VMState state)
		{
			try
			{
				using (ManagementBaseObject inParams = machineObject.GetMethodParameters(HyperVUtils.RequestStateChange))
				{
					inParams["RequestedState"] = state;

					HyperVUtils.InvokeMethod(machineObject, HyperVUtils.RequestStateChange, inParams, true);
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private void InitSnapshots()
		{
			snapshots = new List<ManagementObject>();
			using (ManagementObjectCollection settingsCollection = machineObject.GetRelated("Msvm_VirtualSystemSettingData", "Msvm_SnapshotOfVirtualSystem", null, null, null, null, false, null))
			{
				foreach (ManagementObject setting in settingsCollection)
				{
					using (setting)
					{
						string systemType = (string)setting["VirtualSystemType"];
						if (string.Compare(systemType, HyperVUtils.RealizedSnapshot, StringComparison.OrdinalIgnoreCase) == 0)
						{
							snapshots.Add(setting);
						}
					}
				}
			}
		}

		private VMState State
		{
			get
			{
				var s = (UInt16)machineObject["EnabledState"];
				return (VMState)s;
			}
		}

		private string GetDnsName()
		{
			if (State == VMState.On)
			{
				var kvpExchangeComponents = machineObject.GetRelated(HyperVUtils.ExchangeComponent);
				if (kvpExchangeComponents.Count != 1)
				{
					throw new Exception("Could not get Exchange component");
				}

				foreach (ManagementObject kvpExchangeComponent in kvpExchangeComponents)
				{
					foreach (string exchangeDataItem in (string[])kvpExchangeComponent["GuestIntrinsicExchangeItems"])
					{
						XPathDocument xpathDoc = new XPathDocument(new StringReader(exchangeDataItem));
						XPathNavigator navigator = xpathDoc.CreateNavigator();
						navigator = navigator.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'FullyQualifiedDomainName']");
						if (navigator != null)
						{
							navigator = navigator.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()");
							return navigator.Value;
						}
					}
				}
			}
			return "Unknown";
		}
	}
}
